package calcsdk;

import calcsdk.nw.AsyncWriter;
import calcsdk.nw.Example;
import calcsdk.nw.FindDist;
import calcsdk.nw.ITreePro;
import calcsdk.nw.Matrix;
import calcsdk.nw.MethodPotential;
import calcsdk.nw.TestBaseSolve;
import calcsdk.nw.Util;
import calcsdk.test.FindDistExt;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author Develop
 */
public class Calcsdk implements Runnable {
    
    public static final int M_VETVEY = 0;
    public static final int M_METL = 1;
    public static final int M_FAST_BASE = 2;
	public static final int M_TEST = 3;
	
	public static final int OUT_PRINT_NONE=0;
	public static final int OUT_PRINT_SOLUTIONS=1;
	public static final int OUT_PRINT_PATHS=2;
	public static final int OUT_PRINT_LOOPS=4;
	public static final int OUT_PRINT_ALL=OUT_PRINT_SOLUTIONS | OUT_PRINT_PATHS | OUT_PRINT_LOOPS;
	
    //public List<Matrix> mx = new ArrayList<Matrix>();
    //public float maxPatchLen = 60;
    public int startPatch = 0;
    public int endPatch = 0;
    public boolean avoidCycles = true;
    public boolean useFictive = false;
    public List<int[]> path = new ArrayList<int[]>();
    public float shortestPathLen = Float.POSITIVE_INFINITY;
    public ITreePro core = null;
    public boolean DEBUG = true;
    public int PRINT_MATRIX = OUT_PRINT_ALL;
    public AsyncWriter matrixWriter = null;
    public AsyncWriter pathWriter = null;
    public AsyncWriter mainWriter = null;
    private Thread thread = null;
    public int daughterThreadNumb = 0;
    public int daughterThreadMax = 2;
    public float[][] origMas;
    public int[] nodMax;
    public int method;
    public int processedMatrices = 0;
    public int maxGeneration = 0;
    public boolean isPatch = false;
    public ThreadPoolExecutor service;
    public int maximumThreads = 10;
    public long startTime=0;
    private String fileName="test";
    public boolean transit[]=null;
	public Calcsdk daughterSdk=null;

    /**
     * "Метод ветвей и границ"
     * @param core TreePro
     * @param mas Данные соединительных линий
     * @param nodMax Данные максимумов фиктивных узлов
     * @param patch Вывод пути/времени
     * @param avoidCycles Разрешить "Обход зависания узлов"
     * @param maxLen Максимальный путь
     * @param start Стартовый путь
     * @param end Конечный путь
     * @param fileName Имя файлы вывода
     */
    public Calcsdk(ITreePro core, float[][] mas, int[] nodMax, boolean avoidCycles, boolean patch,
            int printMatrix, float maxLen, int start, int end, String fileName, boolean transit[]) {
        this(core, mas, nodMax, M_VETVEY, avoidCycles, printMatrix, patch, false, maxLen, start, end, fileName, transit);
    }

    /**
     * "Метод fastBase"
     * @param core TreePro
     * @param mas Данные соединительных линий
     * @param patch Вывод пути/времени
     * @param start Стартовый путь
     * @param end Конечный путь
     * @param fileName Имя файлы вывода
     */
    public Calcsdk(ITreePro core, float[][] mas, boolean patch, int start, int end, String fileName) {
        this(core, mas, null, M_FAST_BASE, false, OUT_PRINT_NONE, patch, false, Float.POSITIVE_INFINITY,
                start, end, fileName, null);
    }
	
	/**
     * "Метод test"
     * @param core TreePro
     * @param mas Данные соединительных линий
	 * @param avoidCycles Разрешить "Обход зависания узлов"
     * @param patch Вывод пути/времени
     * @param start Стартовый путь
     * @param end Конечный путь
     * @param fileName Имя файлы вывода
     */
    public Calcsdk(ITreePro core, float[][] mas, boolean avoidCycles, boolean patch, int printMatrix, int start, int end, String fileName) {
        this(core, mas, null, M_TEST, avoidCycles, printMatrix, patch, false, Float.POSITIVE_INFINITY,
                start, end, fileName, null);
    }

    /**
     * "Метод метлы"
     * @param core TreePro
     * @param mas Данные соединительных линий
     */
    public Calcsdk(ITreePro core, float[][] mas, String fileName) {
        this(core, mas, null, M_METL, false, OUT_PRINT_NONE, false, false, Float.POSITIVE_INFINITY, 0, 0,
                fileName, null);
    }

    /**
     * Общий конструктор
     * @param core TreePro
     * @param mas Данные соединительных линий
     * @param nodMax Данные максимумов фиктивных узлов
     * @param avoidCycles Разрешить "Обход зависания узлов"
     * @param printMatrix Вывод промежуточных матриц
     * @param patch Вывод пути/времени
     * @param debug Вывод служебной информации
     * @param maxLen Максимальный путь
     * @param start Стартовый путь
     * @param end Конечный путь
     * @param fileName Имя файлы вывода
     * @param transit Проходить ли вершину всегда насквозь (для фиктивных)
     */
    public Calcsdk(ITreePro core, float[][] mas, int[] nodMax, int method,
            boolean avoidCycles, int printMatrix, boolean patch, boolean debug, float maxLen,
            int start, int end, String fileName, boolean transit[]) {
        this.core = core;
        this.origMas = mas;
        this.nodMax = nodMax;
        this.DEBUG = debug;
        this.method = method;
        this.PRINT_MATRIX = printMatrix;
        this.avoidCycles = avoidCycles;
        this.isPatch = patch;
        this.startPatch = start;
        this.endPatch = end;
        this.fileName=fileName;
        this.transit=transit;
        init();
    }
    
    public final void init() {
        startTime=System.currentTimeMillis();
        maximumThreads *= Runtime.getRuntime().availableProcessors();
        service = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                0, TimeUnit.NANOSECONDS,
                new SynchronousQueue<Runnable>());//Executors.newCachedThreadPool();
        
        System.out.println("Core: " + Runtime.getRuntime().availableProcessors());
        if (core != null) {
            core.linkPatch(path);
        }
        thread = new Thread(this);
        thread.setName("Worker");
        thread.start();
        //service.submit(this);
    }
    
    public synchronized boolean pathAdd(int[] arr, Matrix sender) {
		//сдвиг до необходимой начальной вершины
		int t[];
		if (startPatch!=-1) {
			int pos=Util.arraySearch(arr, startPatch);
			t=new int[arr.length];
			for (int i=0;i<arr.length-1;i++)
				t[i]=arr[(pos+i)%(arr.length-1)];
			t[arr.length-1]=startPatch;
			arr=t;
		}
        //System.out.println(System.nanoTime());
        int i;
        for (i = 0; i < path.size(); i++) {
            if (Arrays.equals(path.get(i), arr)) {
                break;
            }
        }
        if (i == path.size()) {
            path.add(arr);
            if (core != null) {
                // TODO: 12456
                core.addPatch(shortestPathLen, arr);
            }
            //System.out.println("\nprev path len path: ("+shortestPathLen+")");
            String str = null;
            if (DEBUG || pathWriter != null) {
                str = pathToString(arr, false) + "\r\n";
            }
            if (DEBUG && sender!=null) {
                System.out.print("new path:\n" + str + sender.patchesToString(0) + "\r\n\r\n");
            }
            if (pathWriter != null && sender!=null) {
                pathWriter.add(str + sender.patchesToString(1) + "\r\n\r\n");
            }
            //System.out.println("\n");
			return true;
        }
		return false;
    }
    
    public static String pathToString(int[] arr, boolean unmarkFictive) {
        String res = "";
        for (int j = 0; j < arr.length-1; j++) {
            int name = arr[j];
            if (unmarkFictive) {
                name = name > 1000 ? name / 1000 : name + 1;
            } else {
                name++;
            }
            res += name + "->";
        }
        int name = arr[0];
        if (unmarkFictive) {
            name = name > 1000 ? name / 1000 : name + 1;
        } else {
            name++;
        }
        res += name;
        return res;
    }


    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
//        ExecutorService service = Executors.newCachedThreadPool();
//        for(int i = 0; i < 10; i++) {
//         service.submit(new Runnable() {
//          public void run() {
//           while (true) {}
//          }
//         });
//        }
        
        int n = 5, max = 100;
        
        Random rnd = new Random();
        long s = 798;//System.currentTimeMillis()%1000;//650;//19;//
        rnd.setSeed(s);
        
        float startMasRandom[][];// = new float[n][];
        startMasRandom = new float[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) {
                    startMasRandom[i][j] = Float.NaN;
                } else {
                    startMasRandom[i][j] = rnd.nextInt(max);
                }
            }
        }

        //System.out.println("Seed: " + s);
        float startMasRandomBase[][] = new float[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                if ((i <= 1 && j <= 1) || (i > 1 && j > 1)) {
                    startMasRandomBase[i][j] = Float.NaN;
                    startMasRandomBase[j][i] = Float.NaN;
                } else {
                    startMasRandomBase[i][j] = rnd.nextInt(max);
                    startMasRandomBase[j][i] = startMasRandomBase[i][j];
                }
            }
        }
        int nm[] = new int[n];
        nm[0] = n - 2;
        nm[1] = n - 2;
        for (int i=0;i<n;i++) nm[i]=1;
        //TestBaseSolve.processSimm(startMasRandomBase);
        //Calcsdk calcsdk = new Calcsdk(null, startMasRandomBase, nm, true, true, false, 9999, 1, 0, "test.xml");//startMas4
        //Calcsdk calcsdk = new Calcsdk(null, startMasBug7, null, true, true, true, 9999, 0, -1, "", null);
        //Calcsdk calcsdk = new Calcsdk(null, fde.getDist(true), null, true, true, false, 9999, 1, 0, "test.xml", null);//simple
        //Calcsdk calcsdk = new Calcsdk(null, Example.startMasSolve0, false, true, OUT_PRINT_ALL, 0, -1, "");//test method
        //new FindDistExt(startMasBug6, null);
        //Calcsdk calcsdk = new Calcsdk(null, startMasRandomBase, true, 0, 0);
        MethodPotential methodPotential = new MethodPotential();
    }
    
    @Override
    public void run() {
        
        switch (method) {
            case M_VETVEY:
                startMethodVetvey();
                break;
            case M_METL:
                startMethodMetl();
                break;
            case M_FAST_BASE:
                startMethodFastBase();
                break;
			case M_TEST:
				startMethodTest();
				break;
        }



//		Thread currentThread = Thread.currentThread();
//		ThreadGroup group = currentThread.getThreadGroup();
//		int activeCount = group.activeCount();


        //findDist.find(startMas,0);
    }
    
    private float[][] initMas() {
        int n = 5, max = 100;
        
        float startMasRandom[][];// = new float[n][];
        Random rnd = new Random();
        startMasRandom = new float[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) {
                    startMasRandom[i][j] = Float.NaN;
                } else {
                    startMasRandom[i][j] = rnd.nextInt(max);
                }
            }
        }
        float startMasRandomBase[][] = new float[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if ((i <= 1 && j <= 1) || (i > 1 && j > 1)) {
                    startMasRandomBase[i][j] = Float.NaN;
                } else {
                    startMasRandomBase[i][j] = rnd.nextInt(max);
                }
            }
        }
        
        if (nodMax == null) {
            useFictive = false;
        } else {
            for (int is : nodMax) {
                if (is > 0) {
                    useFictive = true;
                    break;
                }
            }
        }
        
        origMas = (origMas == null) ? startMasRandom : origMas;
        //float startMas[][] = Util.arrayCopy(origMas);
        return Util.arrayCopy(origMas);
    }
    
    private void startMethodMetl() {
        //float arr[][]=new float[origMas.length][];
        AsyncWriter wr = null;
        try {
            wr = new AsyncWriter("метод метлы.txt",fileName);
        } catch (IOException ex) {
            if (core != null) {
                core.finish("Ошибка открытия файла:\r\n" + ex, isPatch);
            }
        }
        float res[][] = new float[origMas.length][];
        String ret = "\t";
        for (int i = 0; i < origMas.length; i++) {
            ret += "\t  " + (i + 1);
        }
        ret += "\r\n";
        for (int i = 0; i < origMas.length; i++) {
            //arr[i]=
            res[i] = FindDist.find(initMas(), i, this);
            ret += "Из вершины " + (i + 1) + ":\t" + Util.arrayToString(res[i]) + "\r\n\r\n";
        }
        wr.add(ret);
        wr.stopAndWait();
        if (core != null) {
            core.finish(ret, isPatch, res);
        }
    }
    
    public void stop() {
        //if (service.isShutdown()) {
        //    service.shutdownNow();
        //} else {
		if (daughterSdk!=null) daughterSdk.stop();
            service.shutdown();
        //}
//		Queue<Runnable> queue=service.getQueue();
//		for (Runnable r=queue.poll();r!=null;r=queue.poll()) {
//			
//		}
    }
    
    private void startMethodVetvey() {
//        new Thread() {
//
//            @Override
//            public void run() {
//                while (processedMatrices >= 0) {
//                    System.out.println(processedMatrices+" "+maxGeneration);
//                    try {
//                        Thread.sleep(1);
//                    } catch (InterruptedException e) {
//                    }
//                }
//            }
//        }.start();
		if (DEBUG) System.out.println(Util.arrayToJavaCode(origMas));
		
        try {
			if ((PRINT_MATRIX & OUT_PRINT_SOLUTIONS)>0) mainWriter = new AsyncWriter("solutions.txt",fileName);
			if ((PRINT_MATRIX & OUT_PRINT_PATHS)>0) pathWriter = new AsyncWriter("paths.txt",fileName);
            if ((PRINT_MATRIX & OUT_PRINT_LOOPS)>0) matrixWriter = new AsyncWriter("loop_matrices.txt",fileName);
        } catch (IOException e) {
        }
        
        float t[][] = initMas();
        Matrix matrix = new Matrix(t, this);
        
        while ((matrix.threadFinished + matrix.threadError < matrix.threadStarted || matrix.threadStarted == 0) && !service.isShutdown()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
            }
        }
        
        service.shutdown();
        
        processedMatrices = -1;
        //matrix.print(origMas);

        
        String finStr = "Исходная матрица:\r\n" + Util.arrayToString(origMas,1) + "\r\n";
        finStr += "Транспонированная матрица:\r\n";
        finStr += Util.arrayToString(Util.arrayTranspose(origMas),1) + "\r\n";
        finStr += "Минимальная длина: " + (shortestPathLen != Float.POSITIVE_INFINITY ? shortestPathLen + "\r\n" + "Пути:\r\n" : "не найдена. Решений нет.");
        finStr += elapsedTime()+"\r\n\r\n";
        for (int i = 0; i < path.size(); i++) {
            finStr += pathToString(path.get(i), true) + "\r\n";
        }
        
        if (DEBUG) {
            System.out.println(finStr);
        }
        if (mainWriter != null) {
            mainWriter.add(finStr);
            mainWriter.stop();
        }
        if (matrixWriter != null) {
            matrixWriter.stop();
        }
        if (pathWriter != null) {
            pathWriter.add(finStr);
            pathWriter.stop();
        }
        
        if (DEBUG) {
            System.out.println("WAIT WRITERS");
        }
        
        if (mainWriter != null) {
            mainWriter.waitForStop();
        }
        if (matrixWriter != null) {
            matrixWriter.waitForStop();
        }
        if (pathWriter != null) {
            pathWriter.waitForStop();
        }
        
        if (DEBUG) {
            System.out.println("CORE FIN");
        }
        
        if (core != null) {
            core.finish(finStr, isPatch);
        }
        
        if (DEBUG) {
            System.out.println("DONE ALL");
        }
    }
    
    private void startMethodFastBase() {
        try {
            mainWriter = new AsyncWriter("fast_solutions.txt",fileName);
        } catch (IOException e) {
        }
        String ret = "Исходная матрица:\r\n" + Util.arrayToString(origMas) + "\r\n";
        ret += "Транспонированная матрица:\r\n" + Util.arrayToString(Util.arrayTranspose(origMas)) + "\r\n";
        ret += "Путь:\r\n";
        ret += TestBaseSolve.processSimm(initMas(), this) + "\r\n";
        if (mainWriter != null) {
            mainWriter.add(ret);
            mainWriter.stopAndWait();
        }
        if (core != null) {
            core.finish(ret, isPatch);
        }
    }
	
	private void startMethodTest() {
		try {
			if ((PRINT_MATRIX & OUT_PRINT_SOLUTIONS)>0) mainWriter = new AsyncWriter("solutions.txt",fileName);
			if ((PRINT_MATRIX & OUT_PRINT_PATHS)>0) pathWriter = new AsyncWriter("paths.txt",fileName);
            //if ((PRINT_MATRIX & OUT_PRINT_LOOPS)>0) matrixWriter = new AsyncWriter("loop_matrices.txt",fileName);
        } catch (IOException e) {
        }
		
		
		FindDistExt fde=new FindDistExt(origMas, null);
        daughterSdk = new Calcsdk(null, fde.getDist(true), null, avoidCycles, isPatch, OUT_PRINT_LOOPS, shortestPathLen, startPatch, endPatch, fileName, null);
		
		//если найден новый путь(пути), обрабатываем и пишем в общий файл.
		float daughterPathLen=Float.POSITIVE_INFINITY;
		int prevPathNumb=0;
		do {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				daughterSdk.service.shutdown();
			}
			
			if (daughterPathLen!=daughterSdk.shortestPathLen) {daughterPathLen=daughterSdk.shortestPathLen; prevPathNumb=0; path.clear();}
			
			if (daughterSdk.path.size()>prevPathNumb) {
				for (int i=prevPathNumb;i<daughterSdk.path.size();i++) {
					int[] p=fde.extractPath(daughterSdk.path.get(i), 0, startPatch, endPatch);
					
					if (endPatch!=-1)
						shortestPathLen=Util.getPathLen(origMas, p);
					else
						shortestPathLen=daughterPathLen;
					
					if (pathAdd(p, null)) pathWriter.add(pathToString(p, true)+" ("+Util.getPathLen(origMas, p)+")\r\n");
					if (endPatch==-1) {//если "из->в" задано, обратный путь не подойдёт
						p=Util.arrayInvert(p);
						if (pathAdd(p, null)) pathWriter.add(pathToString(p, true)+" ("+Util.getPathLen(origMas, p)+")\r\n");
					}
				}
				prevPathNumb=daughterSdk.path.size();
			}
		} while (daughterSdk.thread.isAlive());
		
		if (DEBUG) System.out.println("Extracting:");
		for (int[] i : daughterSdk.path) {
			int[] p=fde.extractPath(i, 0, startPatch, endPatch);
			if (DEBUG) {
				System.out.println("Lenght: "+Util.getPathLen(origMas, p));
				Util.arrayPrint(i);
				Util.arrayPrint(p);
			}
			pathAdd(p, null);
			pathAdd(Util.arrayInvert(p), null);
		}
		
		
		String finStr = "Исходная матрица:\r\n" + Util.arrayToString(origMas,1) + "\r\n";
        finStr += "Транспонированная матрица:\r\n";
        finStr += Util.arrayToString(Util.arrayTranspose(origMas),1) + "\r\n";
        finStr += "Минимальная длина: " + (daughterSdk.shortestPathLen != Float.POSITIVE_INFINITY ? daughterSdk.shortestPathLen + "\r\n" + "Пути:\r\n" : "не найдена. Решений нет.");
        finStr += elapsedTime()+"\r\n\r\n";
        for (int i = 0; i < path.size(); i++) {
            finStr += pathToString(path.get(i), true) + "\r\n";
        }
        
        if (DEBUG) {
            System.out.println(finStr);
        }
        if (mainWriter != null) {
            mainWriter.add(finStr);
            mainWriter.stop();
        }
//        if (matrixWriter != null) {
//            matrixWriter.stop();
//        }
		if (pathWriter != null) {
            pathWriter.add(finStr);
            pathWriter.stop();
        }
        
        if (DEBUG) {
            System.out.println("WAIT WRITERS");
        }
        
        if (mainWriter != null) {
            mainWriter.waitForStop();
        }
//        if (matrixWriter != null) {
//            matrixWriter.waitForStop();
//        }
		if (pathWriter != null) {
            pathWriter.waitForStop();
        }
        
        if (DEBUG) {
            System.out.println("CORE FIN");
        }
        
        if (core != null) {
            core.finish(finStr, isPatch);
        }
        
        if (DEBUG) {
            System.out.println("DONE ALL");
        }
	}
    
	
    public String elapsedTime() {
        return "(время расчёта "+Util.getDiffTime(startTime, System.currentTimeMillis())+")";
    }
    
    public void onError(String str) {
        service.shutdownNow();
        core.error(str);
    }
}
